/** @file    JabberGlooxBundle.cpp
 *  @author  Alessandro Polo
 *  @version $Id: JabberGlooxBundle.cpp 2882 2010-08-09 11:08:30Z alex $
 *  @brief
 * File containing methods for the wosh::services::JabberGlooxBundle class.
 * The header for this class can be found in JabberGlooxBundle.h, check that file
 * for class description.
 ****************************************************************************/
/* Copyright (c) 2007-2010, WOSH - Wide Open Smart Home 
 * by Alessandro Polo - OpenSmartHome.com
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the OpenSmartHome.com WOSH nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY Alessandro Polo ''AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL Alessandro Polo BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ****************************************************************************/

 #include "JabberGlooxBundle.h"
 
#ifdef libgloox09
# include "JabberGloox09.h"
#else
# include "JabberGloox10.h"
#endif

 #include <core/ObjectAllocator.h>
 #include <core/MethodsCommon.h>
 #include <core/MessageFilter.h>
 #include <core/Fact.h>
 #include <core/List.h>
 #include <core/Table.h>
 #include <core/WoshKernel.h>
 #include <core/DeviceManager.h>
 #include <core/UserManager.h>
 #include <core/SessionManager.h>
 #include <core/SystemError.h>
 #include <core/BusManager.h>
 #include <core/DataModels.h>
 #include <framework/communication/CommunicationEvents.h>


using namespace std;
namespace wosh {
 namespace devices {

 WOSH_REGISTER(wosh::devices::JabberGlooxBundle, "wosh::interfaces::devices::JabberGloox", _JabberGlooxBundle_VERSION, _static_JabberGlooxBundle1 )
 WOSH_REGISTER(wosh::devices::JabberGlooxBundle, "wosh::interfaces::services::CommunicatorService", _JabberGlooxBundle_VERSION, _static_JabberGlooxBundle2 )
 WOSH_REGISTER(wosh::devices::JabberGlooxBundle, "wosh::BundleGeneric", _JabberGlooxBundle_VERSION, _static_JabberGlooxBundle3 )

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS

JabberGlooxBundle::JabberGlooxBundle()
	: BundleGeneric() {
	BundleGeneric::setName( _JabberGloox_NAME, false );
	Properties.update( _KEY_Version, _JabberGlooxBundle_VERSION );

//Log.setLevel( LOG_VERBOSE );

	Log(LOG_DEBUG, " Configuring JabberGloox10 worker.." );
#ifdef libgloox09
	this->jabbWorker = new JabberGloox09(*this);
#else
	this->jabbWorker = new JabberGloox10(*this);
#endif
	this->jabbWorker->setServer( "", _JabberGloox_DEFAULT_ServerPort, "" );
	this->jabbWorker->setAccount( "", "" );
	this->jabbWorker->setStatusMessage( _JabberGloox_DEFAULT_StatusMsg );
	this->jabbWorker->setAutoReconnectMode( _JabberGloox_DEFAULT_AutoReconnectMode );
	this->jabbWorker->setAutoReconnectDelay( _JabberGloox_DEFAULT_AutoReconnectDelay );
	this->jabbWorker->setAutoReconnectRetries( _JabberGloox_DEFAULT_AutoReconnectRetries );
	this->jabbWorker->setJabberGlooxListener(this);

	Log(LOG_DEBUG, " Setting (XMPP) Credentials [SecurityTokenProvider3rdparty].." );
	CredentialsIO.setAuthorityName("google.com");
	CredentialsIO.setAuthorityChannel(wosh::security::CHANNEL_TCP);
	CredentialsIO.setAuthenticationMode(wosh::SecurityToken::AUTH_PASSWORD, "SASL");

	Log(LOG_DEBUG, " Setting default properties and permissions.." );
	Properties.set( _CommunicatorService_KEY_ChannelType,	(int)Notification::TYPE_TEXTUAL, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_ChannelLink,	(int )Communicator::LINK_SYNCH, Permission(Permission::Read) );
	Properties.set( _CommunicatorService_KEY_Cost,			0.0, Permission::RW_R__R__() );
	Properties.set( _CommunicatorService_KEY_Priority,		50, Permission::RW_RW_R__() );
	Properties.set( _CommunicatorService_KEY_Reliability,	70, Permission::RW_RW_R__() );
	Properties.set( _CommunicatorService_KEY_Protocol,		(int)Notification::PROTOCOL_SHELL + (int)Notification::PROTOCOL_HUMAN, Permission::RW_R__R__() );
#ifdef libgloox09
	Properties.set( _JabberGloox_KEY_LibraryVersion,		"libgloox09", Permission(Permission::Read) );
#else
	Properties.set( _JabberGloox_KEY_LibraryVersion,		"libgloox10", Permission(Permission::Read) );
#endif
	Properties.set( _JabberGloox_KEY_ServerAddress,			"", Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_ServerPort,			_JabberGloox_DEFAULT_ServerPort, Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_ServerProxy,			"", Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_UserName,				"",	Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_UserPassword,			"",	Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_StatusMessage,			_JabberGloox_DEFAULT_StatusMsg, Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_UserAccountKey,		_JabberGloox_DEFAULT_UserAccountKey, Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_AcceptSubscription,	_JabberGloox_DEFAULT_AcceptSubscription, Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_AcceptUnSubscription,	_JabberGloox_DEFAULT_AcceptUnSubscription, Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_AutoReconnectMode,		_JabberGloox_DEFAULT_AutoReconnectMode, Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_AutoReconnectDelay,	Variant_TS(_JabberGloox_DEFAULT_AutoReconnectDelay*1000), Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_AutoReconnectRetries,	_JabberGloox_DEFAULT_AutoReconnectRetries, Permission::RW_RW_R__() );
	Properties.set( _JabberGloox_KEY_DefaultProtocol,		(int)Notification::PROTOCOL_SHELL, Permission::RW_R__R__() );
	Properties.set( _JabberGloox_KEY_DefaultLanguage,		_JabberGloox_DEFAULT_Language, Permission::RW_R__R__() );
	Properties.set( _JabberGloox_KEY_DefaultTargetWoshUser,	_JabberGloox_DEFAULT_TargetUser, Permission::RW_R__R__() );
	Properties.monitor(_JabberGloox_KEY_ServerAddress);
	Properties.monitor(_JabberGloox_KEY_ServerPort);
	Properties.monitor(_JabberGloox_KEY_UserName);
	Properties.monitor(_JabberGloox_KEY_UserPassword);
	Properties.monitor(_JabberGloox_KEY_AutoReconnectMode);
	Properties.monitor(_JabberGloox_KEY_AutoReconnectDelay);
	Properties.monitor(_JabberGloox_KEY_AutoReconnectRetries);

	Log(LOG_DEBUG, " Registering methods.." );
	MethodMessageResponse* mmSendMessageToUser = Methods.create<MethodMessageResponse>( _CommunicatorService_METHOD_sendmessage, "send message to (wosh) username", Permission(Permission::RX) );
	mmSendMessageToUser->setMethod( this, (MethodMessageResponsePtr)&JabberGlooxBundle::mmDoSendMessageToUser );

	MethodList* mmListContacts = Methods.create<MethodList>( _METHOD_List, "List contacts", Permission::R_XR_XR__() );
	mmListContacts->setDataFieldExtractor( DataFieldExtractor<ContactInfo>::createFor< DataAdapterMapT<string, ContactInfo> >(this->contacts, "Contacts") );

	MethodRetrieve* mmRetrieveContacts = Methods.create<MethodRetrieve>( _METHOD_Retrieve, "Retrieve contact(s)", Permission::R_XR_XR__() );
	mmRetrieveContacts->setItemExtractor( ObjectExtractor<ContactInfo, const std::string&>::createFor< DataAdapterMapT<string, ContactInfo> >(this->contacts, &ContactInfo::getUserID ) );

	MethodRequest* mmContactAdd = Methods.create<MethodRequest>( _JabberGloox_METHOD_ContactAdd, "Add contact", Permission::R_XR_XR__() );
	mmContactAdd->setMethod( this, (MethodRequestPtr)&JabberGlooxBundle::mmDoContactAdd );

	MethodRequest* mmContactRemove = Methods.create<MethodRequest>( _JabberGloox_METHOD_ContactRemove, "Remove contact", Permission::R_XR_XR__() );
	mmContactRemove->setMethod( this, (MethodRequestPtr)&JabberGlooxBundle::mmDoContactRemove );

	MethodRequest* mmContactsSynch = Methods.create<MethodRequest>( _JabberGloox_METHOD_ContactSynch, "Synch contacts against WOSH users", Permission::R_XR_XR__() );
	mmContactsSynch->setMethod( this, (MethodRequestPtr)&JabberGlooxBundle::mmDoContactsSynch );

	setBundleState(Bundle::STATE_CREATED, false);
 }

JabberGlooxBundle::~JabberGlooxBundle() {
	Log(LOG_INFO, " Destroying.." );
	if ( isBundleRunning() ) {
		Log(LOG_WARNING, "~JabberGlooxBundle() : Destroying while RUNNING! Trying to stop.." );
		bundleStop();
	 }
	Log(LOG_DEBUG, ":~JabberGlooxBundle() : Destroying JabberWorker.." );
	delete this->jabbWorker; this->jabbWorker = NULL;
	Log(LOG_VERBOSE, ":~JabberGlooxBundle() : Destroyed." );
 }

//////////////////////////////////////////////////////////////////////////////////////////////// CONSTRUCTORS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL

WRESULT JabberGlooxBundle::bundleStart() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STARTING) ) return WRET_ERR_WRONG_STATE;

	if ( this->jabbWorker->getServerAddress().empty() ||
		 this->jabbWorker->getServerPort() == 0 ||
		 this->jabbWorker->getAccountName().empty() ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "Server/Account was NOT Configured!" );
		return WRET_ERR_PARAM;
	 }

	WRESULT bus_connected_sec = BusSecurity.connect( _Bus_Security, Bus::ACTION_DEFERRED );
	if ( WFAILED(bus_connected_sec) )
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "bundleStart", "FAILED#%d Connecting Bus ("_Bus_Security")..", bus_connected_sec );

	WRESULT ret = BundleGeneric::start_SynchThread( this->jabbWorker );

	Response* response = new Response( _METHOD_ListProperties, Properties.toTable(false) );
	Message* message = new Message(response);
	message->setSource(this);
	message->getDestination().fromString("*:Communication");
	signMessage(message);
	BusCore.postMessage(message);

	Log(LOG_DEBUG, ":bundleStart() : Registering as Device.." );
	wosh::Device* myself = new wosh::Device(*this);
	myself->getInterfaces() = this->Interfaces;
	WoshKernel::getInstance()->devices().registerDevice( myself );

	// BUNDLE-STATE (STARTED) will be updated async by WORKER, through call: thread_event()
	return ret;
 }

WRESULT JabberGlooxBundle::bundleStop() {
	if ( !BundleGeneric::bundleValidate_StartStop(Bundle::STATE_STOPPING) ) return WRET_ERR_WRONG_STATE;

	WRESULT ret = BundleGeneric::stop_SynchThread( this->jabbWorker );

	Log(LOG_DEBUG, ":bundleStop() : UnRegistering as Device.." );
	WoshKernel::getInstance()->devices().unRegisterDevice( this->getName() );

	// unregister all sessions
	this->contacts.transactionBeginWrite();
	tStrContactInfoMap::ConstIterator it;
	const tStrContactInfoMap::ConstIterator it_end = this->contacts.end();
	for ( it=this->contacts.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		if ( it->second->getSession() == NULL ) continue;
		WoshKernel::getInstance()->sessions().unRegisterSession(it->second->getSession());
		delete it->second->getSession(); it->second->setSession(NULL);
	 }
	this->contacts.transactionEnd();
	// BUNDLE-STATE (STOPPED) will be updated async by WORKER, through call: thread_event()
	return ret;
 }

////////////////////////////////////////////////////////////////////////////////////////////// BUNDLE CONTROL
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES

void JabberGlooxBundle::busMessage( const Message& message, const Bus* source ) {
	if ( message.isEmpty() ) return;
	if ( message.getContent()->isNotification() ) {
		if ( message.getDestination().isBroadcast() ) return;
		if ( !MessageFilter::isMessageTarget(message, this) ) return;
		if ( !isBundleRunning() ) return;
		const Notification* notif = message.getContent()->asNotification();
		WRESULT sent = notify( notif );
		Message* reply = message.replyTo( new Response( _CommunicatorService_RESPONSE_notif_sent, new Variant(notif->getID()), sent ) );
		signMessage(reply);
		BusCore.postMessage(reply);
		if ( WSUCCEEDED(sent) ) {
			long ts = getUserLastSeen( notif->getRecipent_User() );
			if ( ts > 0 && Utilities::std_time() - ts < 10 ) {
				Message* reply2 = message.replyTo( new Response( _CommunicatorService_RESPONSE_notif_read, new Variant(notif->getID()) ) );
				signMessage(reply2);
				BusCore.postMessage(reply2);
			 }
		 }
	 }
//	DeviceManager::busMessageForBundleDevices( message, source, this, this->Methods, this->Log, this->BusCore, this->BusDevices );
	BundleGeneric::busMessage(message, source);
 }

//////////////////////////////////////////////////////////////////////////////////////// PROCESS BUS MESSAGES
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS

bool JabberGlooxBundle::updatingProperty( bool& do_update, const Variant& value_proposed, Property& property_current, const PropertiesProvider* source ) {
	if ( property_current.getKey() == _JabberGloox_KEY_ServerAddress ||
		 property_current.getKey() == _JabberGloox_KEY_ServerPort ||
		 property_current.getKey() == _JabberGloox_KEY_UserName ||
		 property_current.getKey() == _JabberGloox_KEY_UserPassword )
	 {
		if ( this->jabbWorker->isThreadRunning() ) return false;
		bool ret = false;
		if ( property_current.getKey() == _JabberGloox_KEY_ServerAddress )
			ret = this->jabbWorker->setServerAddress( value_proposed.toString() );
		else if ( property_current.getKey() == _JabberGloox_KEY_ServerPort )
			ret = this->jabbWorker->setServerPort( value_proposed.toInteger(_JabberGloox_DEFAULT_ServerPort) );
		else if ( property_current.getKey() == _JabberGloox_KEY_ServerPort )
			ret = this->jabbWorker->setServerProxy( value_proposed.toString() );
		else if ( property_current.getKey() == _JabberGloox_KEY_UserName )
			ret = this->jabbWorker->setAccountUserName( value_proposed.toString() );
		else if ( property_current.getKey() == _JabberGloox_KEY_UserPassword ) {
			ret = this->jabbWorker->setAccountPassword( value_proposed.toString() );
			property_current.setValue("*********");
			do_update = false;
		 }
		else if ( property_current.getKey() == _JabberGloox_KEY_StatusMessage )
			ret = this->jabbWorker->setStatusMessage( value_proposed.toString() );
		return ret;
	 }
	else if ( property_current.getKey() == _JabberGloox_KEY_AutoReconnectMode ) {
		this->jabbWorker->setAutoReconnectMode( value_proposed.toInteger(this->jabbWorker->getAutoReconnectMode()) );
		return true;
	 }
	else if ( property_current.getKey() == _JabberGloox_KEY_AutoReconnectDelay ) {
		this->jabbWorker->setAutoReconnectMode( value_proposed.toLong(this->jabbWorker->getAutoReconnectDelay()) );
		return true;
	 }
	else if ( property_current.getKey() == _JabberGloox_KEY_AutoReconnectRetries ) {
		this->jabbWorker->setAutoReconnectMode( value_proposed.toLong(this->jabbWorker->getAutoReconnectRetries()) );
		return true;
	 }
	else // forward processing to base class
		return BundleGeneric::updatingProperty(do_update, value_proposed, property_current, source);
 }

///////////////////////////////////////////////////////////////////////////////////////////// PROPERTY EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

ContactInfo* JabberGlooxBundle::checkCreateAccount_( const std::string& accountname ) {
	// recognize the user
	string username = getUsernameFromAccountName(accountname);
	if ( username == "" ) {
		SystemError::raise(this, Log, SystemError::WARNING, 0, "checkCreateAccount_", "Account %s not recognized", accountname.c_str() );
		return NULL;
	 }
	///@todo EVAL USER/SOURCE vs. SECURITY RULES here
	Log(LOG_INFO, ":checkCreateAccount_() : Creating Slot for %s [%s]", accountname.c_str(), username.c_str() );
	ContactInfo* info = new ContactInfo();
	this->contacts.set(accountname, info);
	info->setUser( username );
	info->setUserID( accountname );
	int proto = Properties.getValueOf(_JabberGloox_KEY_DefaultProtocol).toInteger(-1);
	info->setProtocol( (Notification::PROTOCOL)proto );
	Log(LOG_DEBUG, ":checkCreateAccount_() : Protocol of %s set to %d", info->getUser().c_str(), (int)info->getProtocol() );
	Variant langPref = UserManager::getUserProperty(info->getUser(), "LanguagePreferred");
	if ( langPref.isStringNotEmpty() )
		info->setLanguage( langPref.asString() );
	else {
		std::string lang = Properties.getValueOf( _JabberGloox_KEY_DefaultLanguage ).toString();
		if ( !lang.empty() )
			info->setLanguage( lang );
		else
			info->setLanguage( _JabberGloox_DEFAULT_Language );
	 }
	Log(LOG_DEBUG, ":checkCreateAccount_() : Language of %s set to %s", info->getUser().c_str(), info->getLanguage().c_str() );
	std::string TargetUser = Properties.getValueOf( _JabberGloox_KEY_DefaultTargetWoshUser ).toString();
	info->setLastTargetUser( _JabberGloox_DEFAULT_TargetUser );
	Log(LOG_DEBUG, ":checkCreateAccount_() : TARGET User of %s set to %s", info->getUser().c_str(), info->getLastTargetUser().c_str() );
	setupUserSession(info); // all synch! (and locked)
	return info;
 }

WRESULT JabberGlooxBundle::setupUserSession( ContactInfo* info ) {
	if ( info == NULL ) return WRET_ERR_PARAM;
	SessionProxy* session = new SessionProxy();
	Log(LOG_INFO, ":setupUserSession(%s) : Initializing SessionProxy#%ld%", info->getUser().c_str(), session->getID() );
	session->setCreatorURI(getURI());
	session->setUsernameLogged( info->getUser() );
	session->setListener(this);
	session->setCredentials(&this->CredentialsIO);
	session->setRestrictions( Session::ALLOW_NOTHING );
	// registerSession must be called while NOT locked, because of callback
	WRESULT ret = WoshKernel::getInstance()->sessions().registerSession(session);
	if ( WFAILED(ret) ) {
		session->setListener(NULL);
		delete session; session = NULL;
		return WRET_ERR_INTERNAL;
	 }
	// see session_registration
	return WRET_OK;
 }

WRESULT JabberGlooxBundle::doContactsSynch( bool add, bool remove ) {
	// get the list WOSH users who have a valid jabber-account (non-empty)
	std::string userAccountKey = Properties.getValueOf(_JabberGloox_KEY_UserAccountKey).toString();
	std::vector<std::string> users_wosh = UserManager::findUserByProperty( userAccountKey, Variant() );
	Log(LOG_VERBOSE, ":doContactsSynch(%d,%d) : %d users found {%s}", add, remove, users_wosh.size(), wosh::Utilities::joinVector(users_wosh, ";").c_str() );
	// it's better to create two lists (sub, unsub) and release the mutex
	// because of: contacts are updated (write) in callbacks, even if it shouldn't deadlock
	if ( remove ) {
		std::vector<std::string> unsub_accounts;
		// account->user not found ('JabberAccount' was removed/cleaned?)
		this->contacts.transactionBeginRead();
		std::vector<std::string>::const_iterator it_wu;
		tStrContactInfoMap::ConstIterator it;
		const tStrContactInfoMap::ConstIterator it_end = this->contacts.end();
		for ( it=this->contacts.begin(); it!=it_end; ++it ) {
			std::vector<std::string> users = UserManager::findUserByProperty( userAccountKey, Variant(it->first) );
			if ( users_wosh.empty() ) {
				// no users mapped to this account
				unsub_accounts.push_back(it->first);
				continue;
			 }
			if ( it->second == NULL ) continue;
			// one or more users mapped to this account.. make sure current is one of them
			bool found = false;
			for ( it_wu=users.begin(); it_wu!=users.end(); it_wu++ ) {
				if ( *it_wu == it->second->getUser() ) {
					found = true;
				 }
			 }
			if ( !found )
				unsub_accounts.push_back(it->first);
		 }
		this->contacts.transactionEnd();
		Log(LOG_INFO, ":doContactsSynch(%d,%d) : Adding %d users found {%s}", add, remove, unsub_accounts.size(), wosh::Utilities::joinVector(unsub_accounts, ";").c_str() );
		for ( it_wu=unsub_accounts.begin(); it_wu!=unsub_accounts.end(); it_wu++ ) {
			this->jabbWorker->unSubscribe( *it_wu, "WOSH JabberGloox [Synchonization]" );
		 }
	 }

	if ( add ) {
		std::vector<std::string> sub_accounts;
		this->contacts.transactionBeginRead();
		std::vector<std::string>::const_iterator it;
		for ( it=users_wosh.begin(); it!=users_wosh.end(); it++ ) {
			string userAccount = UserManager::getUserProperty( *it, userAccountKey).toString();
			if ( userAccount.empty() ) {
				Log(LOG_WARNING, ":doContactsSynch(%d,%d) : Empty key for User %s. Skipped.", add, remove, (*it).c_str() );
				continue;
			 }
			ContactInfo* cInfo = this->contacts.find(userAccount);
			if ( cInfo != NULL && cInfo->getUser() == *it )
				continue; // already synched
			sub_accounts.push_back(userAccount);
		 }
		this->contacts.transactionEnd();
		Log(LOG_INFO, ":doContactsSynch(%d,%d) : Adding %d users found {%s}", add, remove, sub_accounts.size(), wosh::Utilities::joinVector(sub_accounts, ";").c_str() );
		for ( it=sub_accounts.begin(); it!=sub_accounts.end(); it++ ) {
			this->jabbWorker->subscribe( *it, "WOSH JabberGloox [Synchonization]" );
		 }
	 }
	return WRET_OK;
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////// INTERNAL COMMANDS

bool JabberGlooxBundle::parseInternalCommand( const std::string& message, ContactInfo* info ) {
	if ( message.find("%") != 0 ) return false;
	if ( message.find("%INFO") == 0 ) {
		Log(LOG_DEBUG, ":parseInternalCommand() : Internal CMD by %s [%s]", message.c_str(), info->getUser().c_str() );
		string summary = "JabberGloox Informations:\n";
		summary += "ACCOUNT: " + info->getUserID() + "\n";
		summary += "WOSH-USER: " + info->getUser() + "\n";
		summary += "\n";
		summary += "TARGET: " + info->getLastTargetUser() + "\n";
		summary += "LANG: " + info->getLanguage() + "\n";
		summary += "PROTO: " + Utilities::toString<int>(info->getProtocol()) + "[" + Notification::getProtocolAsString(info->getProtocol()) + "]\n";
		this->jabbWorker->sendMessage( info->getUserID(), summary );
		return true;
	 }
	else if ( message.find("%HELP") == 0 ) {
		Log(LOG_DEBUG, ":parseInternalCommand() : Internal CMD by %s [%s]", message.c_str(), info->getUser().c_str() );
		string summary = "JabberGloox Commands:\n";
		summary += "%INFO : print summary of current session/settings\n";
		summary += "%HELP : show this list\n";
		summary += "%TARGET <USER> : set target user to <USER> [default: wosh]\n";
		summary += "%LANG <LANG> : set language to <LANG> [default: en_GB]\n";
		summary += "%PROTO <INT> : set protocol to <INT> [default 1(SHELL)]\n";
		this->jabbWorker->sendMessage( info->getUserID(), summary );
		return true;
	 }
	else if ( message.find("%TARGET") == 0 ) {
		Log(LOG_DEBUG, ":parseInternalCommand() : Internal CMD by %s [%s]", message.c_str(), info->getUser().c_str() );
		string new_target = message.substr(7);
		Utilities::trimSpaces(new_target);
		info->setLastTargetUser(new_target);
		if ( !UserManager::isUser(new_target) ) {
			Log(LOG_WARNING, ":parseInternalCommand() : Target Recipent of %s set to '%s' (User Unknown!?)", info->getUser().c_str(), info->getLastTargetUser().c_str() );
			this->jabbWorker->sendMessage( info->getUserID(), "JabberGloox: TargetUser set to '" + info->getLastTargetUser() + "' (but user was not found)" );
				 }
		else {
			Log(LOG_INFO, ":parseInternalCommand() : Target Recipent of %s set to '%s'", info->getUser().c_str(), info->getLastTargetUser().c_str() );
			this->jabbWorker->sendMessage( info->getUserID(), "JabberGloox: TargetUser set to '" + info->getLastTargetUser() + "'" );
		 }
		this->jabbWorker->setStatusMessage(new_target);
		return true;
	 }
	else if ( message.find("%LANG") == 0 ) {
		Log(LOG_DEBUG, ":parseInternalCommand() : Internal CMD by %s [%s]", message.c_str(), info->getUser().c_str() );
		string new_language = message.substr(5);
		Utilities::trimSpaces(new_language);
		info->setLanguage( new_language );
		Log(LOG_INFO, ":parseInternalCommand() :Language of %s set to %s", info->getUser().c_str(), info->getLanguage().c_str() );
		this->jabbWorker->sendMessage( info->getUserID(), "JabberGloox: Language set to " + info->getLanguage() );
		return true;
	 }
	else if ( message.find("%PROTO") == 0 ) {
		Log(LOG_DEBUG, ":parseInternalCommand() : Internal CMD by %s [%s]", message.c_str(), info->getUser().c_str() );
		string new_proto = message.substr(6);
		Utilities::trimSpaces(new_proto);
		int inp = Utilities::fromString<int>(new_proto);
		if ( inp == 0 ) {
			Log(LOG_CRITICAL, ":parseInternalCommand() : INVALID value of %PROTO [%s]", message.c_str() );
			return true;
		 }
		info->setProtocol( (Notification::PROTOCOL)inp );
		Log(LOG_INFO, ":parseInternalCommand() :Protocol of %s set to %d", info->getUser().c_str(), (int)info->getProtocol() );
		this->jabbWorker->sendMessage( info->getUserID(), string("JabberGloox: Protocol set to ") + Notification::getProtocolAsString(info->getProtocol()) );
		return true;
	 }
	return false;
 }

/////////////////////////////////////////////////////////////////////////////////////////// INTERNAL COMMANDS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////// GTALKBOT EVENTS

void JabberGlooxBundle::jabbbot_Message( const std::string& accountname, const std::string& message, JabberGlooxImpl* ) {
	this->contacts.transactionBeginWrite();
	ContactInfo* info = this->contacts.find(accountname);
	if ( info == NULL ) {
		info = checkCreateAccount_( accountname );
		if ( info == NULL ) {
			this->contacts.transactionEnd();
			return;
		}
	 }
	// update contacts' timestamp 
	info->setLastSeen();
	Log(LOG_VERBOSE, ":jabbbot_Message() : Recognized %s as %s", accountname.c_str(), info->getUser().c_str() );
	if ( message.find("%") == 0 ) {
		bool exit = parseInternalCommand(message, info);
		if ( exit ) {
			this->contacts.transactionEnd();
			return;
		 }
	 }
	// inject message into WOSH system
	TextMessage* txtmsg = NULL;
	if ( info->getSession() != NULL ) {
		info->getSession()->parseInput(message);
	 }
	else {
		txtmsg = new TextMessage();
		txtmsg->setSender_ID( accountname );
		txtmsg->setSender_User( info->getUser() );
		txtmsg->setRecipent_ID( getAccountNameFromUsername(info->getLastTargetUser()) );
		txtmsg->setRecipent_User( info->getLastTargetUser() );
		txtmsg->setProtocol( info->getProtocol() );
		txtmsg->setLanguage( info->getLanguage() );
		txtmsg->setPlain( message );
	 }
	this->contacts.transactionEnd();

	Message* msg_event = new Message( new UserInteraction_Event(info->getUser()) );
	msg_event->setDestinationBroadcast(true);
	signMessage(msg_event); // i trust that event
	BusSecurity.postMessage(msg_event);

	if ( txtmsg != NULL ) {
		// inject TEXT_MESSAGE into WOSH
		Message* msg_txt = new Message( txtmsg );
		msg_txt->setDestinationBroadcast(true);
		msg_txt->setSource(this);
		CredentialsIO.signMessage( msg_txt, txtmsg->getSender_User(), txtmsg->getSender_ID() );
		BusCore.postMessage(msg_txt);
	 }
 }

void JabberGlooxBundle::jabbbot_UserStatus( const std::string& accountname, int presence_status, const std::string& status_message, JabberGlooxImpl* ) {
	if ( this->jabbWorker == NULL ) return;
	if ( accountname == this->jabbWorker->getAccountName() ) {
		Log(LOG_DEBUG, ":jabbbot_UserStatus(%s) : I'm online, hopefully i knew already :)", accountname.c_str() );
		return;
	 }
	this->contacts.transactionBeginWrite();
	ContactInfo* info = this->contacts.find(accountname);
	if ( info == NULL ) {
		info = checkCreateAccount_( accountname );
		if ( info == NULL ) {
			Log(LOG_CRITICAL, ":jabbbot_UserStatus(%s) : Unable to Create/get account", accountname.c_str() );
			this->contacts.transactionEnd();
			return;
		 }
	 }
	string username = info->getUser();
	Fact* fact = new UserPresence_StateChanged( info->getUser(), (User::PRESENCE)presence_status, info->getLastSeen() );
	// update contacts' timestamp 
	if ( (presence_status & User::PRESENCE_OFFLINE) == User::PRESENCE_OFFLINE ) {
		info->setLastSeen( -Utilities::std_time() );
		info->setUserStatus(User::STATUS_LOGGEDOUT);
	 }
	else if ( (presence_status & User::PRESENCE_ONLINE) == User::PRESENCE_ONLINE  ) {
		info->setLastSeen();
		info->setUserStatus(User::STATUS_LOGGEDIN);
	 }
	else {
		info->setLastSeen(0);
		info->setUserStatus(User::STATUS_UNKNOWN);
		Log(LOG_WARNING, ":jabbbot_UserStatus() : Ignored unhandled status %d of %s [%s]", presence_status, username.c_str(), accountname.c_str() );
	 }
	Log(LOG_INFO, ":jabbbot_UserStatus() : User %s is %s (as %s)", username.c_str(), User::getUserPresenceAsString((User::PRESENCE)presence_status), accountname.c_str() );
	info->setUserPresence( (User::PRESENCE)presence_status );
	info->setStatusMessage(status_message);
	this->contacts.transactionEnd();
	// notify user presence
	Message* msg_event = new Message( fact );
	msg_event->setDestinationBroadcast(true);
	signMessage(msg_event); // i trust that event
	BusSecurity.postMessage(msg_event);
 }

short JabberGlooxBundle::jabbbot_SubscriptionChanged( const std::string& accountname, int status, JabberGlooxImpl* ) {
	if ( this->jabbWorker == NULL ) return -1;
	if ( accountname == this->jabbWorker->getAccountName() ) {
		// umh?
		return 0;
	 }
	if ( status == _JabberGloox_Subscription_Subscribe && !Properties.getValueOf(_JabberGloox_KEY_AcceptSubscription).toBoolean(true) ) {
		Log(LOG_INFO, ":jabbbot_SubscriptionChanged(%d) : Ignoring Subscription of User '%s'", status, accountname.c_str() );
		return -1;
	 }
	if ( status == _JabberGloox_Subscription_UnSubscribe && !Properties.getValueOf(_JabberGloox_KEY_AcceptUnSubscription).toBoolean(true) ) {
		Log(LOG_INFO, ":jabbbot_SubscriptionChanged(%d) : Ignoring UnSubscription of User '%s'", status, accountname.c_str() );
		return -1;
	 }
	this->contacts.transactionBeginWrite();
	ContactInfo* info = this->contacts.find(accountname);
	if ( info == NULL ) {
		if ( status == _JabberGloox_Subscription_UnSubscribed )
			return 1; // then don't add it
		info = checkCreateAccount_( accountname );
		if ( info == NULL ) {
			this->contacts.transactionEnd();
			return 0;
		 }
	 }
	else {
		if ( status == _JabberGloox_Subscription_UnSubscribed ) { // then remove from contact list
			this->contacts.erase(accountname, true);
			return 1;
		 }
	 }
	Log(LOG_INFO, ":jabbbot_SubscriptionChanged(%d) : Accepting subscription of User '%s' [%s]", status, info->getUser().c_str(), accountname.c_str() );
	this->contacts.transactionEnd();
	return 1;
 }

///////////////////////////////////////////////////////////////////////////////////////////// GTALKBOT EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////// SESSION EVENTS

void JabberGlooxBundle::session_registration( short reg_unreg, wosh::communication::SessionProxy* session ) {
	if ( session == NULL ) return;
	// callback is always SYNCH and already locked!
	//  - JabberGlooxBundle::checkCreateAccount_() -> SessionManager::registerSession() -> SessionProxy::registered() -> HERE
	//  - JabberGlooxBundle::bundleStop() ->SessionManager::unRegisterSession() -> SessionProxy::unregistered() -> HERE
	tStrContactInfoMap::ConstIterator it;
	const tStrContactInfoMap::ConstIterator it_end = this->contacts.end();
	for ( it=this->contacts.begin(); it!=it_end; ++it ) {
		if ( it->second == NULL ) continue;
		if ( it->second->getUser() != session->getUsernameLogged() ) continue;
		if ( reg_unreg == 1 ) { // registered
			it->second->setSession(session);
			session->login();
			session->open();
			Log(LOG_INFO, ":session_registration(%d,%p) : Session#%ld OPENED[%s]", reg_unreg, session, session->getID(), it->first.c_str() );
		 }
		else if ( reg_unreg == 0 ) { // unregistered
			it->second->setSession(NULL);
			session->close();
			Log(LOG_INFO, ":session_registration(%d,%p) : Session#%ld CLOSED [%s]", reg_unreg, session, session->getID(), it->first.c_str() );
		 }
		return;
	 }
	Log(LOG_CRITICAL, ":session_registration(%d,%p) : Session#%ld MAPPING NOT FOUND", reg_unreg, session, session->getID() );
 }

WRESULT JabberGlooxBundle::session_print_message( const std::string& message, wosh::communication::SessionProxy* session ) {
	if ( session == NULL ) return WRET_ERR_PARAM;
	return sendMessageToUser( session->getUsername(), message );
 }

////////////////////////////////////////////////////////////////////////////////////////////// SESSION EVENTS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

std::string JabberGlooxBundle::getUsernameFromAccountName( const std::string& accountname ) {
	std::string userAccountKey = Properties.getValueOf(_JabberGloox_KEY_UserAccountKey).toString();
	std::vector<std::string> users = UserManager::findUserByProperty( userAccountKey, Variant(accountname) );
	if ( users.size() == 0 ) {
		Log(LOG_WARNING, ":getUsernameFromAccountName() : User '%s' not recognized! [Property '%s' missing?]", accountname.c_str(), userAccountKey.c_str() );
		return "";
	 }
	else if ( users.size() > 1 ) {
		Log(LOG_WARNING, ":getUsernameFromAccountName() : User '%s' not recognized!", accountname.c_str() );
		return "";
	 }
	return users[0];
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

std::string JabberGlooxBundle::getAccountNameFromUsername( const std::string& username ) {
	// retrieve the jabber account mapped to the user, first check if user exists
	bool found = UserManager::isUser(username);
	if ( !found ) {
		Log(LOG_WARNING, ":getAccountNameFromUsername() : wosh-User '%s' not recognized! [not found]", username.c_str() );
		return "";
	 }
	// get the KEY (name) (of Users' properties) where the account name is stored, default is 'JabberAccount'
	std::string userAccountKey = Properties.getValueOf(_JabberGloox_KEY_UserAccountKey).toString();
	// get the 'JabberAccount' property of the target user, it should store his own account name, such as john.doe@gmail.com
	return UserManager::getUserProperty(username, userAccountKey).toString();
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////// COMMUNICATOR INTERFACE

bool JabberGlooxBundle::isUserReachable( const std::string& username ) {
	std::string accountname = getAccountNameFromUsername(username);
	if ( accountname == "" ) return false;
	MutexLockerRead mL(this->contacts.getMutex());
	ContactInfo* info = this->contacts.find(accountname);
	if ( info == NULL ) return false;
	// negative means he is offline from abs(.)
	if ( info->isOffline() ) return false;
	return true;
 }

bool JabberGlooxBundle::isUserRegistered( const std::string& username ) {
	std::string accountname = getAccountNameFromUsername(username);
	if ( accountname == "" ) return false;
	if ( !this->contacts.exists(accountname) ) return false;
	return true;
 }

long JabberGlooxBundle::getUserLastSeen( const std::string& username ) {
	std::string accountname = getAccountNameFromUsername(username);
	if ( accountname == "" ) return 0;
	MutexLockerRead mL(this->contacts.getMutex());
	ContactInfo* info = this->contacts.find(accountname);
	if ( info == NULL )	return 0;
	// negative means he is offline from abs(.)
	if ( info->isOffline() ) return -info->getLastSeen();
	return info->getLastSeen();
 }

WRESULT JabberGlooxBundle::sendMessageToUser( const std::string& userWosh, const std::string& message ) {
	std::string accountname = getAccountNameFromUsername(userWosh);
	if ( accountname == "" ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "sendMessageToUser", "FAILED mapping user %s", userWosh.c_str() );
		return WRET_ERR_PARAM;
	 }
	return sendMessageTo( accountname, message );
 }

WRESULT JabberGlooxBundle::sendMessageTo( const std::string& userID, const std::string& message ) {
	if ( userID == "" ) return WRET_ERR_PARAM;
	bool sent = this->jabbWorker->sendMessage( userID, message );
	if ( !sent ) {
		SystemError::raise(this, Log, SystemError::CRITICAL, 0, "sendMessageTo", "[%s] failed sending message", userID.c_str() );
		return WRET_ERR_INTERNAL;
	 }
	return WRET_OK;
 }

WRESULT JabberGlooxBundle::notify( const Notification* notification, const Message* ) {
	if ( notification == NULL ) return WRET_ERR_PARAM;
	///@todo validate notification (textual, !empty)
	// if has custom account, dont map!
	if ( notification->isForAnyone() || notification->getRecipent_User().size() == 0 ) {
		Log(LOG_WARNING, "notify(%ld) to ANYONE", notification->getID(), notification->getRecipent_User().c_str() );
		WRESULT ret = WRET_OK;
		this->contacts.transactionBeginRead();
		tStrContactInfoMap::ConstIterator it;
		const tStrContactInfoMap::ConstIterator it_end = this->contacts.end();
		for ( it=this->contacts.begin(); it!=it_end; ++it ) {
			ret += sendMessageTo( it->first, notification->getPlain() );
		 }
		this->contacts.transactionEnd();
		return ret;
	 }
	Log(LOG_INFO, "notify(%ld) to %s", notification->getID(), notification->getRecipent_User().c_str() );
	return sendMessageToUser( notification->getRecipent_User(), notification->getPlain() );
 }

////////////////////////////////////////////////////////////////////////////////////// COMMUNICATOR INTERFACE
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS

Response* JabberGlooxBundle::mmDoContactAdd( const Request* request ) {
	if ( request == NULL ) return NULL;
	Variant account_name;
	Variant opt_message;
	request->extractArguments(account_name, opt_message );
	if ( this->jabbWorker == NULL )
		return request->replyResponse(WRET_ERR_ILLEGAL_USE, "Bundle is not running!");
	if ( !account_name.isStringNotEmpty() )
		return request->replyResponse(WRET_ERR_ILLEGAL_USE, "invalid contact");
	Log(LOG_VERBOSE, "mmDoContactAdd(%s, %s)", account_name.asString().c_str(), opt_message.toString().c_str() );
	WRESULT ret = this->jabbWorker->subscribe( account_name.asString(), opt_message.toString() );
	if ( WSUCCEEDED(ret) )
		return request->replyResponse(WRET_OK, "Subscription request sent");
	return request->replyResponse(ret, "Subscription request error.");
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* JabberGlooxBundle::mmDoContactRemove( const Request* request ) {
	if ( request == NULL ) return NULL;
	Variant account_name; Variant opt_message;
	request->extractArguments(account_name, opt_message );
	if ( this->jabbWorker == NULL )
		return request->replyResponse(WRET_ERR_ILLEGAL_USE, "Bundle is not running!");
	if ( !account_name.isStringNotEmpty() )
		return request->replyResponse(WRET_ERR_ILLEGAL_USE, "invalid contact");
	Log(LOG_VERBOSE, "mmDoContactRemove(%s, %s)", account_name.asString().c_str(), opt_message.toString().c_str() );
	WRESULT ret = this->jabbWorker->unSubscribe( account_name.asString(), opt_message.toString() );
	if ( WSUCCEEDED(ret) )
		return request->replyResponse(WRET_OK, "UnSubscription request sent");
	return request->replyResponse(ret, "UnSubscription request error");
 }

Response* JabberGlooxBundle::mmDoContactsSynch( const Request* request ) {
	if ( request == NULL ) return NULL;
	string option = request->getArgument().toString();
	Log(LOG_VERBOSE, "mmDoContactsSynch(%s)", option.c_str() );
	bool add = true; bool remove = true;
	if ( option == "add" ) remove = false;
	if ( option == "remove" ) add = false;
	WRESULT ret = doContactsSynch( add, remove );
	if ( WSUCCEEDED(ret) )
		return request->replyResponse(WRET_OK, "(Un)Subscription request(s) sent");
	return request->replyResponse(ret, "(Un)Subscription request error");
 }

//////////////////////////////////////////////////////////////////////////////////////////////////////////////

Response* JabberGlooxBundle::mmDoSendMessageToUser( const Message* requestMessage ) {
	if ( requestMessage == NULL || requestMessage->isEmpty() ) return NULL;
	if ( !requestMessage->getContent()->isRequest() ) return NULL;
	const Request* request = requestMessage->getContent()->asRequest();
	if ( !request->hasData() ) {
		return request->replyResponse(WRET_ERR_PARAM, "Invalid data");
	 }
	std::string username = "";
	std::string message = "";
	if ( request->getData()->isKindOf<List>() ) {
		const List* dl = request->getData()->as<List>();
		if ( dl->size() < 2 || dl->itemAt(0) == NULL ) {
			return request->replyResponse(WRET_ERR_PARAM, "Invalid arguments [username, message]");
		 }
		username = dl->itemAt(0)->toString();
		// BUG DEPRECATED BUG BUG
		//dl->join(message, " ", 1);
	 }
	std::string sender = "";
	if ( requestMessage->getSecurityToken() != NULL )
		sender = requestMessage->getSecurityToken()->getUsername();
	Log(LOG_INFO, "mmDoSendMessageToUser(%s) : %d chars", username.c_str(), message.size() );
	if ( sender != username && sender != "wosh ")
		message += " [by " + sender + "]";
	WRESULT ret = sendMessageToUser( username, message );
	if ( WSUCCEEDED(ret) )
		return request->replyResponse(ret, "Sent!");
	return request->replyResponse(ret, "Internal Error!");
 }

///////////////////////////////////////////////////////////////////////////////////////////////////// METHODS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////

 }; // namespace devices
}; // namespace wosh
